# -*- coding: utf-8 -*-
import random
import subprocess
import time
import logging
import argparse
from datetime import datetime

from appium import webdriver
# from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, TimeoutException

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logger = logging

APP = "XHS"

app_package_map = {
    "XHS": ("com.xingin.xhs", ".index.v2.IndexActivityV2")
}


class XHSAppiumOperate:
    def __init__(self, **kwargs):
        self.screen_width = None
        self.screen_height = None
        self.like_prob = kwargs.get("like_prob", 0.2) / 100
        self.search_prob = kwargs.get("searchProb", 0.2) / 100
        self.searchWord = kwargs.get("searchWord", "").replace("\\n", "\n")
        self.contentKeyWord = kwargs.get("contentKeyWord", "").replace("\\n", "\n").split("\n")
        self.collect_prob = kwargs.get("comment_prob", 0.1) / 100
        self.follow_prob = kwargs.get("follow_prob", 0.05) / 100
        # 仅浏览关注页面
        self.follow_only_mode = kwargs.get("followOnlyMode", 0) != 0
        self.seconds = kwargs.get("seconds", 60 * 10)  # 默认运行10分钟
        self.deviceName = kwargs.get("deviceName")
        self.appium_url = kwargs.get("appium_url")
        self.platformVersion = kwargs.get("platformVersion")
        self.systemPort = kwargs.get("systemPort")
        self.adbPort = kwargs.get("adbPort")

        self.driver = None
        self.max_retries = 3
        self.failure_count = 0

        self.like_count = 0
        self.comment_count = 0
        self.follow_count = 0

    def run(self):
        while self.max_retries > 0:
            try:
                if self.driver:
                    self.driver.quit()
                # 清理clear io.appium.uiautomator2.server storage
                self.connection()
                logger.info(f"设备 {self.deviceName} 连接成功")
                time.sleep(3)

                logger.info("开始执行小红书养号任务...")
                self.script()
                break
            except Exception as e:
                logger.error(f"发生异常: {e}")
                self.failure_count += 1
                self.max_retries -= 1
                if self.max_retries == 0:
                    self.normal_end()
                    raise Exception("任务失败")
        logger.info("任务结束")

    @staticmethod
    def execute_adb_command(adb_command):
        """
        执行单条 ADB 命令

        :param adb_command: 完整的 ADB 命令字符串
        :return: 命令执行结果（stdout）
        """
        try:
            logger.info(f"执行 ADB 命令: {adb_command}")
            result = subprocess.run(
                adb_command,
                shell=True,
                check=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            logger.info(f"命令执行成功: {adb_command}")
            return result.stdout.decode('utf-8')
        except subprocess.CalledProcessError as e:
            logger.error(f"命令执行失败: {adb_command}")
            logger.error(f"错误信息: {e.stderr.decode('utf-8')}")

    def initialize_device_settings(self):
        """
        执行设备初始化所需的 ADB 命令
        """
        logger.info("开始初始化设备设置")

        commands = [
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global adb_keep_usb_enabled 1",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global miui_optimization 0",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global tcp_keepalive 1",
            f"adb -s {self.deviceName} -P {self.adbPort} shell dumpsys battery unplug",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm enable io.appium.uiautomator2.server",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm enable io.appium.uiautomator2.server.test",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global background_process_limit 5",
            f"adb -s {self.deviceName} -P {self.adbPort} shell am set-standby-bucket io.appium.uiautomator2.server ACTIVE",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm trim-caches 500M",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm clear io.appium.uiautomator2.server",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm clear io.appium.uiautomator2.server.test",
            f"adb -s {self.deviceName} -P {self.adbPort} shell input keyevent KEYCODE_CLEAR"]

        for cmd in commands:
            try:
                self.execute_adb_command(cmd)
            except Exception as e:
                logger.error(f"执行命令失败: {cmd}")
                logger.error(f"错误详情: {e}")

    def connection(self):
        self.initialize_device_settings()
        self.view_count = 0
        desired_caps = {
            "platformName": "Android",
            "platformVersion": self.platformVersion,
            "deviceName": self.deviceName,
            "udid": self.deviceName,
            "automationName": "UiAutomator2",
            "appPackage": app_package_map[APP][0],
            "appActivity": app_package_map[APP][1],
            "noReset": True,
            "fullReset": False,
            "systemPort": int(self.systemPort),
            "adbPort": int(self.adbPort),
            "appWaitForLaunch": True,
            "noSign": True,
            "autoLaunch": True,
            "newCommandTimeout": 180,
            "skipLogcatCapture": True,
            "disableWindowAnimation": True,
            "disableAndroidWatchers": True,
            "adbExecTimeout": 30000,
            "uiautomator2ServerInstallTimeout": 60000,
            "skipDeviceInitialization": True,
        }

        self.driver = webdriver.Remote(self.appium_url, desired_caps)
        # self.driver = webdriver.Remote(self.appium_url, options=UiAutomator2Options().load_capabilities(desired_caps))
        self.screen_width, self.screen_height = self.get_window_size()

    def script(self):
        start_time = datetime.now()
        logger.info("开始浏览推荐列表页")

        if self.follow_only_mode:
            logger.info("进行关注tab页")
            follow_tab = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value=f'//android.widget.TextView[@text="关注"]',
                timeout=5
            )
            follow_tab.click()
            time.sleep(random.uniform(1, 10))
            logger.info("成功进入tab页")

        while (datetime.now() - start_time).seconds < self.seconds:
            is_search = False

            # 随机搜索
            if random.random() < self.search_prob and self.follow_only_mode == False:
                search_word_list = self.searchWord.split("\n")
                search_word_list.append("")
                search_word = random.choice(search_word_list)
                if search_word != "":
                    logger.info(f"正在搜索: {search_word}")
                    # 关键词搜索
                    search_button = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[@content-desc="搜索"]',
                        timeout=5
                    )
                    search_button.click()

                    time.sleep(6)

                    search_edit_text = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.EditText[@resource-id="com.xingin.xhs:id/0_resource_name_obfuscated"]',
                        timeout=5
                    )
                    # search_edit_text.clear()
                    search_edit_text.send_keys(search_word)
                    time.sleep(3)
                    search_button = self.find_element_with_retry(
                        by=AppiumBy.ANDROID_UIAUTOMATOR,
                        value='new UiSelector().text("搜索")',
                        timeout=5
                    )
                    search_button.click()
                    is_search = True
                    logger.info(f"完成搜索,下面开始浏览搜索的内容")
            swipe_times = random.randint(10, 20)
            if not is_search:
                swipe_times = random.randint(2, 4)
            logger.info(f"计划浏览{swipe_times}屏内容")
            for i in range(swipe_times):
                self.view_count += 1
                logger.info(f"正在浏览第 {self.view_count} 屏内容")
                self.swipe_to_next()
                time.sleep(random.uniform(1, 10))

                # 随机点击一个笔记或视频入口
                feed_element = None

                try:
                    feed_element_view = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value=f'//androidx.recyclerview.widget.RecyclerView[@resource-id="com.xingin.xhs:id/0_resource_name_obfuscated"]',
                        timeout=5
                    )
                except Exception as e:
                    try:
                        feed_element_view = self.find_element_with_retry(
                            by=AppiumBy.ANDROID_UIAUTOMATOR,
                            value=f'new UiSelector().resourceId("com.xingin.xhs:id/0_resource_name_obfuscated")',
                            timeout=5
                        )
                    except Exception as e:
                        logger.error(f"未找到笔记列表元素,尝试回退按钮,重新来过")
                        break

                # 扫描整个列表页的笔记描述
                posts = feed_element_view.find_elements(
                    AppiumBy.XPATH,
                    '//android.widget.TextView'
                )
                # 打乱 posts 顺序
                # random.shuffle(posts)
                # 遍历所有笔记item
                pure_numbers_list = []  # 用于存储纯数字及其位置
                for index, post in enumerate(posts):
                    content_desc = post.get_attribute("text").replace('万', '0000').replace('+', '').strip()
                    # logger.info(f"{content_desc}")
                    if content_desc.isdigit():
                        # 检查是否为纯数字
                        if content_desc.isdigit():
                            pure_numbers_list.append({
                                'value': int(content_desc),
                                'position': index
                            })
                            # logger.info(f"发现纯数字: {content_desc}, 位置: {index}")
                # 输出找到的纯数字及位置信息
                if pure_numbers_list:
                    # logger.info(f"本屏找到的纯数字列表: {pure_numbers_list}")
                    # 找出value最大值的position
                    max_value_item = max(pure_numbers_list, key=lambda x: x['value'])
                    # logger.info(
                    #     f"最大纯数字(点赞数): {max_value_item['value']}, 位置: {max_value_item['position']},文章标题：{posts[max_value_item['position'] - 3].get_attribute('text')}")

                    feed_element = posts[max_value_item['position'] - 3]

                if self.contentKeyWord != "" and random.random() < 0.9:
                    logger.info(f"查找关键词的笔记")
                    # 遍历所有笔记item
                    for post in posts:
                        content_desc = post.get_attribute("text")
                        # logger.info(f"{content_desc}")
                        for content_word in self.contentKeyWord:
                            if content_word in content_desc:
                                feed_element = post
                                logger.info(f"查找到当前屏包含关键词的笔记关键词：{content_word}")

                if feed_element:
                    feed_element.click()
                    time.sleep(3)

                    try:
                        stop_button = self.find_element_with_retry(
                            by=AppiumBy.XPATH,
                            value='//android.view.ViewGroup[@content-desc="暂停"]',
                            timeout=5
                        )
                        logger.info("进入【视频动态】页面")
                        self.handle_video_page()
                    except Exception as e:
                        logger.warning("播放按钮未找到，可能是笔记页面")
                        logger.info("进入【图文笔记】页面")
                        self.handle_note_page()
                        logger.info("回到列表页")

            if is_search is True:
                logger.info("滚动浏览完成后，从搜索状态，退回到首页")
                self.driver.press_keycode(4)
                time.sleep(5)
                self.driver.press_keycode(4)
                time.sleep(5)
                self.driver.press_keycode(4)

            time.sleep(random.uniform(2, 10))

        self.normal_end()

    def handle_note_page(self):
        try:
            self.find_element_with_retry(
                AppiumBy.XPATH,
                '//android.widget.FrameLayout[contains(@content-desc, "双指左划或右划即可查看更多内容")]'
            )
            logger.info("执行图文笔记页操作")
            if random.random() < 0.9:
                swipe_times = random.randint(1, 3)
                for i in range(swipe_times):
                    self.driver.swipe(
                        self.screen_width * 0.9,
                        self.screen_height / 3,
                        self.screen_width * 0.2,
                        self.screen_height / 3,
                        100
                    )
                    time.sleep(random.uniform(1, 3))  # 每次滑动后稍作停顿
                    logger.info(f"已完成横屏第 {i + 1} 次滑动")
        except Exception as e:
            logger.warning(f"进入笔记详情页失败")
            try:

                self.find_element_with_retry(
                    AppiumBy.ANDROID_UIAUTOMATOR,
                    'new UiSelector().text("关注")'
                )
                time.sleep(5)
                logger.warning(f"当前是直播间，进行退出")
                self.driver.press_keycode(4)
                time.sleep(5)
            except Exception as e:
                logger.warning(f"当前不是直播间，无需退出")
            finally:
                return

        self.driver.swipe(
            self.screen_width / 2,
            self.screen_height * 0.8,
            self.screen_width / 2,
            self.screen_height * 0.4,
            100
        )
        time.sleep(random.uniform(1, 10))

        # 点赞
        if random.random() < self.like_prob:
            try:
                like_button = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.Button[starts-with(@content-desc, "点赞")]',
                    timeout=5
                )
                like_button.click()
                time.sleep(random.uniform(1, 10))
                logger.info("图文笔记点赞成功")
            except Exception as e:
                logger.warning(f"图文笔记点赞失败: {e}")
        time.sleep(random.uniform(3, 10))
        # 收藏
        if random.random() < self.collect_prob:
            try:
                collection_btn = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.Button[starts-with(@content-desc, "收藏")]',
                    timeout=5
                )
                collection_btn.click()
                time.sleep(random.uniform(1, 10))
            except Exception as e:
                logger.warning(f"图文收藏失败: {e}")
        logger.info("从图文笔记详情退回到列表页")
        time.sleep(5)
        self.driver.press_keycode(4)
        time.sleep(5)

    def handle_video_page(self):
        logger.info("执行视频动态页操作")
        # 随机滑动 2~10 次
        if self.searchWord == "":
            swipe_times = random.randint(5, 10)
            logger.info(f"开始向下滑动 {swipe_times} 次，查看更多内容")
        else:
            swipe_times = 1
            logger.info(f"目前是关键词搜索，只查看一次，是有目的性的养号，则不进行滑动视频")

        for i in range(swipe_times):
            # 点赞
            if random.random() < self.like_prob:
                try:
                    like_button = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[starts-with(@content-desc, "点赞")]',
                        timeout=5
                    )
                    like_button.click()
                    logger.info("视频点赞成功")
                    time.sleep(random.uniform(1, 10))
                except Exception as e:
                    logger.warning(f"视频点赞失败: {e}")
            time.sleep(random.uniform(3, 10))
            # 收藏
            if random.random() < self.collect_prob:
                try:
                    collection_btn = self.find_element_with_retry(
                        by=AppiumBy.XPATH,
                        value='//android.widget.Button[starts-with(@content-desc, "收藏")]',
                        timeout=5
                    )
                    collection_btn.click()
                    time.sleep(random.uniform(1, 10))
                except Exception as e:
                    logger.warning(f"视频收藏失败: {e}")
            if swipe_times > 1:
                self.swipe_to_next()
                logger.info(f"第 {i + 1} 次视频滑动完成")
        logger.info("从视频笔记详情退回到列表页")
        time.sleep(5)
        self.driver.press_keycode(4)
        time.sleep(5)

    def swipe_to_next(self):
        # 向上滑动
        self.driver.swipe(
            self.screen_width / 2,
            self.screen_height * 0.95,
            self.screen_width / 2,
            self.screen_height * 0.35,
            100
        )

    def find_element_with_retry(self, by, value, timeout=5):
        try:
            element = WebDriverWait(self.driver, timeout).until(
                EC.presence_of_element_located((by, value))
            )
            return element
        except TimeoutException:
            raise NoSuchElementException(f"找不到元素: {value}")

    def get_window_size(self):
        size = self.driver.get_window_size()
        return size['width'], size['height']

    def normal_end(self):
        logger.info("返回首页并退出")
        try:
            self.driver.press_keycode(3)  # HOME 键
            self.driver.quit()
        except:
            pass


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-j', '--job_id', dest='job_id', type=str, default=None, help='target job_id')
    parser.add_argument('-u', '--appium_url', dest='appium_url', type=str, default=None, help='target appium_url')
    parser.add_argument('-d', '--deviceName', dest='deviceName', type=str, default=None, help='target deviceName')
    parser.add_argument('-s', '--systemPort', dest='systemPort', type=str, default=None, help='target systemPort')
    parser.add_argument('-a', '--adbPort', dest='adbPort', type=str, default=None, help='target adbPort')
    parser.add_argument('-p', '--platformName', dest='platformName', type=str, default=None, help='target platformName')
    parser.add_argument('-ver', '--platformVersion', dest='platformVersion', type=str, default=None,
                        help='target platformVersion')
    parser.add_argument('-v', '--variables', dest='variables', type=str, default='{}', help='target variables')
    parser.add_argument('-seconds', '--seconds', dest='seconds', type=int, default='{}', help='seconds')
    parser.add_argument('-like_prob', '--like_prob', type=float, default=10)
    parser.add_argument('-collect_prob', '--collect_prob', type=float, default=10)
    parser.add_argument('-follow_prob', '--follow_prob', type=float, default=50)
    parser.add_argument('-searchProb', '--searchProb', type=float, default=50)
    parser.add_argument('-accountId', '--accountId', dest='accountId', type=str, default="000", help='target accountId')
    # 搜索关键词
    parser.add_argument('-searchWord', '--searchWord', type=str, default="")
    parser.add_argument('-followOnlyMode', '--followOnlyMode', type=int, default=0)
    # 养号内容关键词
    parser.add_argument('-contentKeyWord', '--contentKeyWord', type=str, default="")
    # parser.add_argument('--follow_prob', type=float, default=0.05)

    args = parser.parse_args()
    XHSAppiumOperate(**vars(args)).run()
